<?php

defined('INTERNAL') || die();

class PieformElementViewchooser
{

    protected static $pagination_js = '';

    public static function get_views_data($data)
    {
        global $USER;
        
        $offset = !empty($data['offset']) ? $data['offset'] : null;
        $limit = !empty($data['limit']) ? $data['limit'] : null;
        
        if(isset($data['search']) && $data['search']){
            $ownedby = (object)array('owner'=>$USER->get('id'));
            return View::view_search($data['search'], null, $ownedby, null, $limit, $offset, false);
        }else{
            return View::get_myviews_data($limit, $offset);            
        }
    }

    /**
     * Builds data for the artefact chooser.
     *
     * This builds three pieces of information:
     *
     * - HTML containing table rows
     * - Pagination HTML and Javascript
     * - The total number of artefacts found
     */
    static function build_data($data, $group=null, $institution=null)
    {
        //global $USER;
        $search = '';
        if (!empty($data['search']) && param_boolean('s'))
        {
            $search = param_variable('search', '');
        }

        $data['search'] = $search;
        $data['offset'] -= $data['offset'] % $data['limit'];

        safe_require('blocktype', $data['blocktype']);
        $blocktypeclass = generate_class_name('blocktype', $data['blocktype']);

        $data['sortorder'] = array(array('fieldname' => 'title', 'order' => 'ASC'));
        if (method_exists($blocktypeclass, 'viewchooser_get_sort_order'))
        {
            $data['sortorder'] = call_static_method($blocktypeclass, 'viewchooser_get_sort_order');
        }

        $r = self::get_views_data($data);
        $views = $r->data;
        $totalviews = $r->count;

        $selectone = $data['selectone'];
        
        $default_value = $data['defaultvalue'];
        $default_value = $selectone && is_array($default_value) ? reset($default_value) : $default_value;
        $default_value = !$selectone ? (array)$default_value : $default_value;
        $elementname = $data['name'];
        $template = isset($data['template']) ? $data['template'] : 'form/viewchooser-element.tpl';

        $result = '';
        if ($views)
        {
            foreach ($views as $view)
            {
                $view = (object)$view;
                // Build the radio button or checkbox for the artefact
                $formcontrols = '';
                if ($selectone)
                {
                    $id = hsc($elementname . '_' . $view->id);
                    $name = hsc($elementname);
                    $value = hsc($view->id);
                    $checked = ($default_value == $view->id) ? ' checked="checked"' : '';
                    $formcontrols .= '<input type="radio" class="radio" id="' . $id . '" name="' . $name . '" value="' . $value . '"' . $checked . '>';
                }
                else
                {
                    $id = hsc($elementname . '_' . $view->id);
                    $name = hsc($elementname) . '[' . hsc($view->id) . ']';
                    $checked = $default_value && in_array($view->id, $default_value) ? ' checked="checked"' : '';
                    $formcontrols .= '<input type="checkbox" id="' . $id . '" name="' . $name . '"' . $checked. ' class="viewid-checkbox checkbox">';
                    
                    $name = hsc($elementname) . '_onpage[]';
                    $value = hsc($view->id);
                    $formcontrols .= '<input type="hidden" name="' .$name . '" value="' . $value . '" class="viewid-onpage">';
                }

                $smarty = smarty_core();
                $smarty->assign('view', (object)$view);
                $smarty->assign('elementname', $elementname);
                $smarty->assign('formcontrols', $formcontrols);
                $result .= $smarty->fetch($template) . "\n";
            }
        }

        $pagination = build_pagination(array(
            'id' => $elementname . '_pagination',
            'class' => 'ac-pagination',
            'url' => View::make_base_url() . (param_boolean('s') ? '&s=1' : ''),
            'count' => $totalviews,
            'limit' => $data['limit'],
            'offset' => $data['offset'],
            'datatable' => $elementname . '_data',
            'jsonscript' => 'view/viewchooser.json.php',
            'firsttext' => '',
            'previoustext' => '',
            'nexttext' => '',
            'lasttext' => '',
            'numbersincludefirstlast' => false,
            'extradata' => array(
                'value' => $default_value,
                'blocktype' => $data['blocktype'],
                'group' => $group,
                'institution' => $institution,
            ),
                ));

        return array($result, $pagination, $totalviews, $data['offset']);
    }

    /**
     * Provides a mechanism for choosing one or more artefacts from a list of them.
     *
     * @param Pieform  $form    The form to render the element for
     * @param array    $element The element to render
     * @return string           The HTML for the element
     */
    static function display(Pieform $form, $element)
    {
        global $USER;

        $value = $form->get_value($element);
        $element['offset'] = param_integer('offset', 0);
        list($html, $pagination, $count) = self::build_data($element, $form->get_property('viewgroup'), $form->get_property('viewinstitution'));

        $smarty = smarty_core();
        $smarty->assign('datatable', $element['name'] . '_data');
        $smarty->assign('views', $html);
        $smarty->assign('pagination', $pagination['html']);

        $formname = $form->get_name();
        $smarty->assign('blockinstance', substr($formname, strpos($formname, '_') + 1));

        self::$pagination_js = $pagination['javascript'];

        $baseurl = View::make_base_url();
        $smarty->assign('browseurl', $baseurl);
        $smarty->assign('searchurl', $baseurl . '&s=1');
        $smarty->assign('searchable', !empty($element['search']));

        return $smarty->fetch('form/viewchooser.tpl');
    }

    static function get_value(Pieform $form, $element)
    {
        $name = $element['name'];

        $global = ($form->get_property('method') == 'get') ? $_GET : $_POST;

        if (isset($global[$name]) || isset($global["{$name}_onpage"]))
        {
            $value = (isset($global[$name])) ? $global[$name] : array();

            if ($element['selectone'])
            {
                if (!$value)
                {
                    return null;
                }

                if (preg_match('/^\d+$/', $value))
                {
                    return intval($value);
                }
            }
            else
            {
                $onpage = (isset($global["{$name}_onpage"])) ? $global["{$name}_onpage"] : array();
                $selected = (is_array($value)) ? array_map('intval', array_keys($value)) : array();
                $default = (is_array($element['defaultvalue'])) ? $element['defaultvalue'] : array();

                // 1) Start with what's currently available
                // 2) Remove everything on the page that was active when submitted
                // 3) Add in everything that was selected
                $value = array_merge(array_diff($default, $onpage), $selected);
                return array_map('intval', $value);
            }

            throw new PieformException("Invalid value for viewchooser form element '$name' = '$value'");
        }

        if (isset($element['defaultvalue']))
        {
            return $element['defaultvalue'];
        }

        return null;
    }

    static function set_attributes($element)
    {
        if (!isset($element['selectone']))
        {
            $element['selectone'] = true;
        }
        if (!isset($element['limit']))
        {
            $element['limit'] = 10;
        }
        if (!isset($element['template']))
        {
            $element['template'] = 'form/viewchooser-element.tpl';
        }
        if (!isset($element['search']))
        {
            $element['search'] = true;
        }

        return $element;
    }

    /**
     * Extension by Mahara. This api function returns the javascript required to 
     * set up the element, assuming the element has been placed in the page using 
     * javascript. This feature is used in the views interface.
     *
     * In theory, this could go upstream to pieforms itself
     *
     * @param Pieform $form     The form
     * @param array   $element  The element
     */
    static function views_js(Pieform $form, $element)
    {

// NOTE: $element['name'] is not set properly at this point
        $element = self::set_attributes($element);
//        $element['name'] = (!empty($element['selectone'])) ? 'viewid' : 'viewids';

        self::$pagination_js = 'var p = ' . self::$pagination_js;


        if (!empty($element['selectone']))
        {
            $viewchooserdata = '';
        }
        else
        {
            $viewchooserdata = 'new viewchooserData();';
        }
        self::$pagination_js .= <<<EOF
var ul = getFirstElementByTagAndClassName('ul', 'artefactchooser-tabs', '{$form->get_name()}_{$element['name']}_container');
var doneBrowse = false;
var browseA = null;
var searchA = null;
var browseTabCurrent = true;
if (ul) {
    forEach(getElementsByTagAndClassName('a', null, ul), function(a) {
        p.rewritePaginatorLink(a);
        if (!doneBrowse) {
            doneBrowse = true;

            browseA = a;
            // Hide the search form
            connect(a, 'onclick', function(e) {
                hideElement('viewchooser-searchform');
                removeElementClass(searchA.parentNode, 'current');
                addElementClass(browseA.parentNode, 'current');
                browseA.blur();
                $('viewchooser-searchfield').value = ''; // forget the search for now, easier than making the tabs remember it
                if (!browseTabCurrent) {
                    {$viewchooserdata}
                    browseTabCurrent = true;
                }
                e.stop();
            });
        }
        else {
            searchA = a;

            // Display the search form
            connect(a, 'onclick', function(e) {
                showElement('viewchooser-searchform');
                removeElementClass(browseA.parentNode, 'current');
                addElementClass(searchA.parentNode, 'current');
                $('viewchooser-searchfield').focus();
                if (browseTabCurrent) {
                    {$viewchooserdata}
                    browseTabCurrent = false;
                }
                e.stop();
            });

            connect('viewchooser-searchfield', 'onkeypress', function(e) {
                if (e.key().code == 13) { // enter pressed - submitting form
                    e.stop();
                    signal('viewchooser-searchsubmit', 'onclick', true);
                }
            });

            // Wire up the search button
            connect('viewchooser-searchsubmit', 'onclick', function(e) {
                if (e._event != true) {
                    e.stop();
                }

                var loc = searchA.href.indexOf('?');
                var queryData = [];
                if (loc != -1) {
                    queryData = parseQueryString(searchA.href.substring(loc + 1, searchA.href.length));
                    queryData.extradata = serializeJSON(p.extraData);
                    queryData.search = $('viewchooser-searchfield').value;
                }

                sendjsonrequest(p.jsonScript, queryData, 'GET', function(data) {
                    var tbody = getFirstElementByTagAndClassName('tbody', null, p.datatable);
                    if (tbody) {
                        if (
                            (document.all && document.documentElement && typeof(document.documentElement.style.maxHeight) != "undefined" && !window.opera)
                            ||
                            (/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent))) {
                            var temp = DIV({'id':'ie-workaround'});
                            temp.innerHTML = '<table><tbody>' + data.data.tablerows + '</tbody></table>';
                            swapDOM(tbody, temp.childNodes[0].childNodes[0]);
                            removeElement(temp);
                        }
                        else {
                            // This does not work in IE and Konqueror, the tbody 
                            // innerHTML property is readonly.
                            // http://www.ericvasilik.com/2006/07/code-karma.html
                            tbody.innerHTML = data['data']['tablerows'];
                        }
                    }

                    {$viewchooserdata}

                    // Update the pagination
                    if ($(p.id)) {
                        var tmp = DIV();
                        tmp.innerHTML = data['data']['pagination'];
                        swapDOM(p.id, tmp.firstChild);

                        // Run the pagination js to make it live
                        eval(data['data']['pagination_js']);

                        // Update the result count
                        var results = getFirstElementByTagAndClassName('div', 'results', p.id);
                        if (results) {
                            results.innerHTML = data['data']['results'];
                        }
                    }
                });
            });
        }
    });
}
EOF;
        if (empty($element['selectone']))
        {
            self::$pagination_js .=<<<EOF
/**
 * Manages the problem of changing pages in the artefact chooser losing what 
 * things were selected/not selected
 */
function viewchooserData() {
    var self = this;

    this.init = function() {
        self.insertElementContainers();
        self.connectPagination();
        self.connectCheckboxes();
        self.scrapeForOnpage();
        self.scrapeForSelected();
    }

    /**
     * Puts two containers into the DOM, that will each contain hidden form elements 
     * - one for all the elements on the current page of results, and one for 
     * the currently selected options.
     *
     * Clears out existing containers instead of making new ones, if containers 
     * already exist. This happens when changing tabs on the artefact chooser
     */
    this.insertElementContainers = function() {
        self.seenElementsContainer     = $('seen-elements-container');
        self.selectedElementsContainer = $('selected-elements-container');

        if (self.seenElementsContainer) {
            // Clear out the list of seen elements
            replaceChildNodes(self.seenElementsContainer);
        }
        else {
            self.seenElementsContainer = DIV({'id': 'seen-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('viewchooser-body', self.seenElementsContainer);
        }

        if (self.selectedElementsContainer) {
            // Clear out the list of selected elements
            replaceChildNodes(self.selectedElementsContainer);
        }
        else {
            self.selectedElementsContainer = DIV({'id': 'selected-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('viewchooser-body', self.selectedElementsContainer);
        }
    }

    /**
     * Connects pagination so that when a page is changed, we are told about it
     */
    this.connectPagination = function() {
        paginatorProxy.addObserver(self);
        connect(self, 'pagechanged', self.pageChanged);
    }

    /**
     * Connects checkboxes so when they're clicked we can deal with it
     */
    this.connectCheckboxes = function() {
        forEach(getElementsByTagAndClassName('input', 'viewid-checkbox', 'viewchooser-body'), function(checkBox) {
            connect(checkBox, 'onclick', partial(self.checkboxClicked, checkBox));
        });
    }

    /**
     * Find all hidden onpage inputs, and move them to the container, otherwise 
     * destroy them if they're already in there (which happens if we go to a 
     * page we've already seen)
     */
    this.scrapeForOnpage = function() {
        forEach(getElementsByTagAndClassName('input', 'viewid-onpage', 'viewchooser-body'), function(i) {
            var append = true;
            forEach(self.seenElementsContainer.childNodes, function(seen) {
                if (seen.value == i.value) {
                    append = false;
                    throw MochiKit.Iter.StopIteration;
                }
            });
            if (append) {
                appendChildNodes(self.seenElementsContainer, i);
            }
            else {
                // Element is surplus to requirements
                removeElement(i);
            }
        });
    }

    /**
     * Find all hidden currently selected inputs, and move them to the selected container
     */
    this.scrapeForSelected = function() {
        forEach(getElementsByTagAndClassName('input', 'viewid-checkbox', 'viewchooser-body'), function(i) {
            if (i.checked) {
                self.ensureSelectedElement(i);
            }
        });
    }

    /**
     * When a checkbox is clicked, update the list of selected inputs
     */
    this.checkboxClicked = function(checkbox) {
        if (checkbox.checked) {
            // Add to the list if it's not there
            self.ensureSelectedElement(checkbox);
        }
        else {
            // Remove from the list if it's there
            self.removeSelectedElement(checkbox);
        }
    }

    /**
     * When a pagination link is clicked, update the list of seen inputs
     */
    this.pageChanged = function(data) {
        self.scrapeForOnpage();
        if (findValue(self.seenOffsets, data.offset) == -1) {
            self.scrapeForSelected();
            self.seenOffsets.push(data.offset);
        }
        else {
            self.syncroniseCheckboxStateFromContainer();
        }
        self.connectCheckboxes();
    }

    /**
     * Ensures that the element we have been given is in the list of selected 
     * elements
     */
    this.ensureSelectedElement = function(element) {
        var append = true;
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                append = false;
                throw MochiKit.Iter.StopIteration;
            }
        });

        if (append) {
            appendChildNodes(self.selectedElementsContainer,
                INPUT({'type': 'hidden', 'name': element.name, 'value': 1})
            );
        }
    }

    /**
     * Ensures that the element we have been given is NOT in the list of 
     * selected elements
     */
    this.removeSelectedElement = function(element) {
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                removeElement(selected);
                throw MochiKit.Iter.StopIteration;
            }
        });
    }

    /**
     * Called when the user browses back to a page they have already seen. They 
     * may have added/removed what they have checked on that page, so we need 
     * to syncronise the display with their choices
     */
    this.syncroniseCheckboxStateFromContainer = function() {
        forEach(getElementsByTagAndClassName('input', 'viewid-checkbox', 'viewchooser-body'), function(checkbox) {
            checkbox.checked = false;
            forEach(self.selectedElementsContainer.childNodes, function(selected) {
                if (selected.name == checkbox.name) {
                    // Checkbox should be checked
                    checkbox.checked = true;
                    throw MochiKit.Iter.StopIteration;
                }
            });
        });
    }

    // Contains hidden elements representing every artefact we have seen, 
    // regardless of whether it has been selected
    this.seenElementsContainer = null;

    // Contains hidden elements representing every artefact that has been 
    // selected on any page we have seen
    this.selectedElementsContainer = null;

    // Pagination offsets we have already seen. We have always seen offset 0 
    // when we begin.
    this.seenOffsets = [0];

    self.init();
}

new viewchooserData();

EOF;
        }
        return self::$pagination_js;
    }

}

/**
 * Provides a mechanism for choosing one or more artefacts from a list of them.
 *
 * @param Pieform  $form    The form to render the element for
 * @param array    $element The element to render
 * @return string           The HTML for the element
 */
function pieform_element_viewchooser(Pieform $form, $element)
{
    return PieformElementViewchooser::display($form, $element);
}

function pieform_element_viewchooser_get_value(Pieform $form, $element)
{
    return PieformElementViewchooser::get_value($form, $element);
}

function pieform_element_viewchooser_set_attributes($element)
{
    return PieformElementViewchooser::set_attributes($element);
}

/**
 * Extension by Mahara. This api function returns the javascript required to 
 * set up the element, assuming the element has been placed in the page using 
 * javascript. This feature is used in the views interface.
 *
 * In theory, this could go upstream to pieforms itself
 *
 * @param Pieform $form     The form
 * @param array   $element  The element
 */
function pieform_element_viewchooser_views_js(Pieform $form, $element)
{
    return PieformElementViewchooser::views_js($form, $element);
}

?>
